package com.satan.elasticsearch.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.DistanceUnit;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.satan.elasticsearch.dao.entity.Hotel;
import com.satan.elasticsearch.doc.HotelDoc;
import com.satan.elasticsearch.dto.req.HotelAddReqDto;
import com.satan.elasticsearch.dto.req.HotelPageReqDto;
import com.satan.elasticsearch.dto.req.HotelSearchReqDto;
import com.satan.elasticsearch.dto.req.HotelUpdateReqDto;
import com.satan.elasticsearch.dto.resp.PageRespDto;
import com.satan.elasticsearch.dto.resp.RestResp;
import com.satan.elasticsearch.manager.mq.HotelMqMsgManager;
import com.satan.elasticsearch.mapper.HotelMapper;
import com.satan.elasticsearch.service.HotelService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Demon
 * @description 针对表【tb_hotel】的数据库操作Service实现
 * @createDate 2024-02-16 00:40:37
 */
@Service
@RequiredArgsConstructor
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel>
        implements HotelService {
    private final ElasticsearchClient esClient;

    private final HotelMqMsgManager amqpMsgManager;
    @Override
    public RestResp search(HotelSearchReqDto dto) throws IOException {
        //准备查询请求
        SearchRequest request = SearchRequest.of(o -> {
            SearchRequest.Builder searchBuilder = o.index("hotel");
            //构建查询条件
            buildSearchCondition(dto, searchBuilder);
            //分页
            int pageNum = dto.getPageNum();
            int pageSize = dto.getPageSize();
            searchBuilder.from((pageNum - 1) * pageSize);
            searchBuilder.size(pageSize);
            //排序
            String sortBy = dto.getSortBy();
            if (sortBy != null && !sortBy.isEmpty()) {
                searchBuilder.sort(s -> s.field(f -> f.field(sortBy).order(SortOrder.Asc)));
            }
            //地理位置排序

            String location = dto.getLocation();
            if (location != null && !location.isEmpty()) {
                String[] split = location.split(",");
                double lat = Double.parseDouble(split[0]);
                double lon = Double.parseDouble(split[1]);
                o.sort(s -> s.geoDistance(g -> g.field("location").order(SortOrder.Asc).location(l -> l.latlon(c -> c.lat(lat).lon(lon))).order(SortOrder.Asc).unit(DistanceUnit.Kilometers)));
            }
            return searchBuilder;
        });
        //解析响应
        SearchResponse<HotelDoc> searchResponse = esClient.search(request, HotelDoc.class);
        return handleResponse(dto, searchResponse);
    }

    @Override
    public RestResp search2(HotelSearchReqDto dto) throws IOException {
        SearchRequest searchRequest = SearchRequest.of(a -> {
            SearchRequest.Builder builder = a.index("hotel");
            buildSearchCondition2(dto, builder);
            return builder;
        });
        SearchResponse<HotelDoc> searchResponse = esClient.search(searchRequest, HotelDoc.class);
        return handleResponse(dto, searchResponse);
    }

    /**
     * 获取过滤器
     *
     * @param dto 酒店搜索请求数据
     * @return 返回过滤器的响应
     * @throws IOException 如果发生IO异常
     */
    @Override
    public RestResp getFilters(HotelSearchReqDto dto) throws IOException {
        // 构建搜索请求
        SearchRequest searchRequest = SearchRequest.of(a -> {
            SearchRequest.Builder builder = a.index("hotel");
            // 条件查询
            buildSearchCondition(dto, builder);
            // 聚合查询
            buildAggregations(builder);
            builder.size(0);
            return builder;
        });
        // 执行搜索请求
        SearchResponse<HotelDoc> searchResponse = esClient.search(searchRequest, HotelDoc.class);
        // 处理响应
        return handleResponse2(dto, searchResponse);
    }

    @Override
    public RestResp getSuggestions(String key) throws IOException {
        SearchRequest searchRequest = SearchRequest.of(a ->
                a.index("hotel2").suggest(b -> b.suggesters("my_suggest", c -> c.text(key).completion(d -> d.field("suggestion").skipDuplicates(true).size(10))))
        );
        SearchResponse<HotelDoc> searchResponse = esClient.search(searchRequest, HotelDoc.class);
        return handleResponse3(searchResponse);
    }



    /**
     * 处理响应3
     * @param searchResponse 搜索响应
     * @return RestResp
     */
    private RestResp handleResponse3(SearchResponse<HotelDoc> searchResponse) {

        // 获取建议
        Map<String, List<Suggestion<HotelDoc>>> suggestions = searchResponse.suggest();

        // 获取第一个建议
        Suggestion<HotelDoc> suggestion = suggestions.get("my_suggest").getFirst();

        // 获取选项
        List<CompletionSuggestOption<HotelDoc>> options = suggestion.completion().options();

        // 获取选项的文本列表
        List<String> list = options.stream().map(CompletionSuggestOption::text).toList();
        // 返回结果
        return RestResp.ok(list);
    }



    /**
     * 处理响应结果
     * @param dto 请求数据
     * @param searchResponse 搜索响应结果
     * @return 处理后的响应结果
     */
    private RestResp handleResponse2(HotelSearchReqDto dto, SearchResponse<HotelDoc> searchResponse) {
        // 打印搜索响应
        // System.out.println(searchResponse);
        // 获取聚合结果
        Map<String, Aggregate> aggregations = searchResponse.aggregations();
        // 打印聚合结果
        // System.out.println(aggregations);
        // 获取品牌聚合结果
        List<String> brandList = getAggregationByName(aggregations, "brand_agg");
        // 获取城市聚合结果
        List<String> cityList = getAggregationByName(aggregations, "city_agg");
        // 获取星级聚合结果
        List<String> starList = getAggregationByName(aggregations, "star_agg");
        // 构建过滤器
        Map<String, List<String>> filters = new HashMap<>(3);
        filters.put("brand", brandList);
        filters.put("city", cityList);
        filters.put("star", starList);
        // 返回处理后的响应结果
        return RestResp.ok(filters);
    }


    private  List<String> getAggregationByName(Map<String, Aggregate> aggregations,String aggName) {
        List<String> list=new ArrayList<>();
        // 获取品牌聚合结果
        Aggregate brandAgg = aggregations.get(aggName);
        // 获取品牌聚合结果的字符串聚合结果
        StringTermsAggregate stermsAggregate = brandAgg.sterms();
        // 获取品牌聚合结果的桶
        Buckets<StringTermsBucket> buckets = stermsAggregate.buckets();
        // 将桶转换为列表
        List<StringTermsBucket> termList = buckets.array();
        termList.forEach(b -> {
            String key = b.key().stringValue();
            list.add(key);
        });
        return list;
    }
    /**
     * 构建搜索请求的聚合信息
     * @param builder 搜索请求的构建器对象
     */
    private void buildAggregations(SearchRequest.Builder builder) {
        // 添加"brand_agg"聚合信息
        builder.aggregations("brand_agg", a -> a.terms(b -> b.field("brand").size(100)));
        // 添加"city_agg"聚合信息
        builder.aggregations("city_agg", a -> a.terms(b -> b.field("city").size(100)));
        // 添加"star_agg"聚合信息
        builder.aggregations("star_agg", a -> a.terms(b -> b.field("starName").size(100)));
    }


    private static void buildSearchCondition2(HotelSearchReqDto dto, SearchRequest.Builder builder) {
        String key = dto.getKey();
        builder.query(b -> b.functionScore(c -> c.query(d -> d.match(e -> e.field("all").query(key))).functions(f -> f.filter(g -> g.term(t -> t.field("isAD").value(true))).weight(10.0)).boostMode(FunctionBoostMode.Multiply)));
    }


    private void buildSearchCondition(HotelSearchReqDto dto, SearchRequest.Builder searchBuilder) {
        BoolQuery boolQuery = BoolQuery.of(o -> {
            String key = dto.getKey();
            //判断key不为空
            if (key != null && !key.isEmpty()) {
                o.must(q -> q.match(m -> m.field("all").query(key)));
            }
            //城市条件
            String city = dto.getCity();
            if (city != null && !city.isEmpty()) {
                o.must(q -> q.term(m -> m.field("city").value(city)));
            }
            //品牌条件
            String brand = dto.getBrand();
            if (brand != null && !brand.isEmpty()) {
                o.must(q -> q.term(m -> m.field("brand").value(brand)));
            }
            //星级条件
            String starName = dto.getStarName();
            if (starName != null && !starName.isEmpty()) {
                o.must(q -> q.term(m -> m.field("starName").value(starName)));
            }
            //价格条件
            Integer minPrice = dto.getMinPrice();
            Integer maxPrice = dto.getMaxPrice();
            if (minPrice != null && maxPrice != null) {
                o.filter(q -> q.range(m -> m.field("price").gte(JsonData.of(minPrice)).lte(JsonData.of(maxPrice))));
            }
            return o;
        });
        searchBuilder.query(q -> q.bool(boolQuery));
    }

    private RestResp handleResponse(HotelSearchReqDto dto, SearchResponse<HotelDoc> response) {
        HitsMetadata<HotelDoc> hitsMetadata = response.hits();
        // 4.1.获取总条数
        long total = hitsMetadata.total().value();
        if (response == null || total == 0) {
            return RestResp.ok();
        }
        List<Hit<HotelDoc>> hits = hitsMetadata.hits();
        List<HotelDoc> hotelDocs = hits.stream().map(hit -> {
            HotelDoc hotelDoc = hit.source();
            List<FieldValue> sort = hit.sort();
            if (sort != null && sort.size() > 0) {
                // 4.2.1.获取排序字段
                FieldValue fieldValue = sort.get(sort.size() - 1);
                // 4.2.2.获取排序值
                Object value = fieldValue.doubleValue();
                hotelDoc.setDistance(value);
            }
            return hotelDoc;
        }).toList();
        PageRespDto<HotelDoc> pageRespDto = PageRespDto.of(dto.getPageNum(), dto.getPageSize(), total, hotelDocs);
        return RestResp.ok(pageRespDto);
    }

    /*增删改查代码*/
    @Override
    public RestResp getPage(HotelPageReqDto dto) {
        //分页查询
        Page<Hotel> page = new Page<>(dto.getPageNum(), dto.getPageSize());
        Page<Hotel> hotelPage =page(page);
        return RestResp.ok(PageRespDto.of(dto.getPageNum(),dto.getPageSize(),hotelPage.getTotal(),hotelPage.getRecords()));
    }

    @Override
    public RestResp saveHotel(HotelAddReqDto dto) {
        Hotel hotel = new Hotel();
        BeanUtils.copyProperties(dto,hotel);
        //随机生成一个id;
        save(hotel);
        amqpMsgManager.sendHotelInsertMsg(hotel.getId());
        return RestResp.ok();
    }

    @Override
    public RestResp updateHotel(HotelUpdateReqDto dto) {
        Hotel hotel = new Hotel();
        BeanUtils.copyProperties(dto,hotel);
        System.out.println(hotel);
        updateById(hotel);
        amqpMsgManager.sendHotelInsertMsg(hotel.getId());
        return RestResp.ok();
    }

    @Override
    public RestResp deleteHotel(Long id) {
        removeById(id);
        amqpMsgManager.sendHotelDeleteMsg(id);
        return RestResp.ok();
    }

}




